﻿using System;
using System.Collections.Generic;
using System.Xml;
using System.Xml.Linq;
using Microsoft.Reputation.Services;

namespace Microsoft.Reputation.Tests.FrontEndCommon
{
    public class MrsObjectConverter
    {
        /// <summary>
        /// Helper method to convert the Reputation object xml into a test MrsObject.
        /// The method assumes that the XElement obj holds xml in the format as it is present in CSDB.
        /// </summary>
        /// <param name="obj">Object holding the xml representation of the reputation object</param>
        /// <param name="type">Type of the object - Url, File etc</param>
        /// <returns>Returns the corresponding MrsObject of the type specified</returns>
        public static MrsReputationObject Convert(XElement obj, uint type)
        {
            List<MrsCategoryReputation> reputationList = null;
            List<MrsMetadata> metadataList = null;
            uint expirationMins = 10080;

            XmlDocument doc = new XmlDocument();
            doc.InnerXml = obj.ToString();
            XmlElement root = doc.DocumentElement;

            XmlNodeList reputationInfoList = root.SelectNodes(@"ReputationInfo/Category");
            XmlNodeList metadataInfoList = root.SelectNodes(@"MetadataInfo/Metadata");
            if (reputationInfoList.Count > 0)
            {
                expirationMins = GetExpirationMinsFromXml(reputationInfoList);
                reputationList = GetReputationFromXml(reputationInfoList);
            }
            if (metadataInfoList.Count > 0)
            {
                metadataList = GetMetadataFromXml(metadataInfoList);
            }
            MrsReputationObject res = new MrsReputationObject(type);
            res.Update(
                long.Parse(root.Attributes[1].Value),
                metadataList,
                reputationList,
                expirationMins
            );

            return res;

        }

        /// <summary>
        /// Helper method to get expiration mins for an reputation object. Calculates the minimum of the expiration
        /// mins for each reputation info and returns it.
        /// </summary>
        /// <param name="reputationInfoList">XmlNodeList containing list of Category nodes</param>
        /// <returns>Minimum expiration mins</returns>
        public static uint GetExpirationMinsFromXml(XmlNodeList reputationInfoList)
        {
            uint defaultExpMins = 10080; //default value
            uint minExp = uint.MaxValue;
            if (reputationInfoList.Count > 0)
            {
                foreach (XmlNode categoryNode in reputationInfoList)
                {
                    XmlNodeReader reader = null;
                    try
                    {
                        reader = new XmlNodeReader(categoryNode);
                        if (reader.Read())
                        {
                            uint curExp = (uint)Convertor.ConvertFromString(typeof(uint), reader.GetAttribute("ExpirationMins"));
                            if (minExp > curExp)
                            {
                                minExp = curExp;
                            }
                        }
                    }

                    finally
                    {
                        if (reader != null)
                        {
                            reader.Close();
                        }
                    }
                }
            }
            if (minExp.Equals(uint.MaxValue))
            {
                minExp = defaultExpMins;
            }
            return minExp;
        }


        /// <summary>
        /// Builds a List of reputation objects out of the given list containing the Categories. 
        /// A ReputationInfoList is a list of categories and their associated reputation
        /// </summary>
        /// <param name="reputationInfoList">XmlNodeList containing list of Category nodes</param>
        /// <returns>List of Reputation objects</returns>
        public static List<MrsCategoryReputation> GetReputationFromXml(XmlNodeList reputationInfoList)
        {
            List<MrsCategoryReputation> reputation = null;

            if (reputationInfoList.Count > 0)
            {
                reputation = new List<MrsCategoryReputation>();
                //A reputation Info node can have multiple category nodes, since one object can belong to multiple categories
                //Eg: A URL can belong to categories Business and Productivity
                foreach (XmlNode categoryNode in reputationInfoList)
                {
                    XmlNodeReader reader = null;
                    try
                    {
                        reader = new XmlNodeReader(categoryNode);

                        if (reader.Read())
                        {
                            reputation.Add(new MrsCategoryReputation((uint)Convertor.ConvertFromString(typeof(uint), reader.GetAttribute("CategoryID")), (byte)Convertor.ConvertFromString(typeof(byte), reader.GetAttribute("Confidence")), GetCategoryInfoFromXml(categoryNode)));
                        }
                    }

                    finally
                    {
                        if (reader != null)
                        {
                            reader.Close();
                        }
                    }
                }
            }
            return reputation;
        }

        /// <summary>
        /// Builds a list if Metadata information for the given category node
        /// Each category node contains a category info node with the list of metadata, eg: HostInheritance
        /// </summary>
        /// <param name="categoryNode">XmlNode containing the category info</param>
        /// <returns>List of metadata objects for the category with their type and value</returns>
        public static List<MrsMetadata> GetCategoryInfoFromXml(XmlNode categoryNode)
        {
            List<MrsMetadata> categoryInfo = null;
            if (categoryNode.HasChildNodes)
            {
                //Category Node first child is the category info node
                if (categoryNode.FirstChild.HasChildNodes)
                {
                    categoryInfo = new List<MrsMetadata>();
                    //A category info node can have multiple metadata elements, eg: HostInheritance, PathInheritance
                    foreach (XmlNode metadataNode in categoryNode.FirstChild.ChildNodes)
                    {
                        XmlNodeReader reader = null;
                        try
                        {
                            reader = new XmlNodeReader(metadataNode);

                            if (reader.Read())
                            {
                                categoryInfo.Add(new MrsMetadata(reader.GetAttribute("Name"), metadataNode.FirstChild.InnerText));
                            }
                        }

                        finally
                        {
                            if (reader != null)
                            {
                                reader.Close();
                            }
                        }
                    }
                }
            }
            return categoryInfo;
        }

        /// <summary>
        /// Builds a metadata list for the given metadata info node.
        /// A metadata info node contains metadata nodes which hold the actual metadata values, eg: Bloom, SHA256
        /// </summary>
        /// <param name="metadataList">XmlNodeList containing the metadata elements</param>
        /// <returns>List of Metadata objects with their type and values</returns>
        public static List<MrsMetadata> GetMetadataFromXml(XmlNodeList metadataList)
        {
            List<MrsMetadata> metadata = new List<MrsMetadata>();
            SqlDataConvertor convertor = new SqlDataConvertor();

            foreach (XmlNode metadataNode in metadataList)
            {
                XmlElement metadataElement = metadataNode as XmlElement;
                string convertMethodKey = GetMetadataValueTypeConversionKey(metadataElement);
                metadata.Add(new MrsMetadata(metadataElement.GetAttribute("Name"), convertor.Convert(convertMethodKey, metadataNode.FirstChild.InnerText)));
            }

            return metadata;
        }

        /// <summary>
        /// In order to convert metadata value we need to get the type from the xml itself.
        /// This method parses the type and returns the type as string.
        /// </summary>
        /// <param name="element">XmlElement representing the metadata node</param>
        /// <returns>Type for the metadata value</returns>
        public static string GetMetadataValueTypeConversionKey(XmlElement element)
        {
            string conversionKey = string.Empty;
            string valueType = string.Empty;

            if (element.HasChildNodes)
            {
                //Metadata node child element is Value node whose attribute is xsi-type
                XmlNode node = element.FirstChild;
                XmlNodeReader reader = new XmlNodeReader(node);
                reader.Read();
                valueType = reader.GetAttribute("xsi-type");
                reader.Close();
            }

            if (valueType.Contains("string"))
            {
                conversionKey = "v_text";
            }
            else if (valueType.Contains("hexBinary"))
            {
                conversionKey = "v_binary";
            }
            return conversionKey;
        }

        /// <summary>
        /// Converts the Delivery Service Reputation Object into Test MrsObject
        /// </summary>
        /// <param name="obj">Delivery Service object holding reputation and metadata</param>
        /// <returns>Test MrsObject</returns>
        public static MrsReputationObject Convert(ReputationObject obj)
        {
            if (obj == null)
            {
                return null;
            }

            MrsReputationObject res = new MrsReputationObject(obj.Type);
            List<MrsMetadata> mdList = null;
            List<MrsCategoryReputation> crList = null;

            if ((obj.Metadata != null) && (obj.Metadata.Data != null))
            {
                mdList = new List<MrsMetadata>();

                foreach (Metadata curMd in obj.Metadata.Data)
                {
                    mdList.Add(Convert(curMd));
                }
            }

            if ((obj.Reputation != null) && (obj.Reputation.Data != null))
            {
                crList = new List<MrsCategoryReputation>();

                foreach (CategoryReputation curCr in obj.Reputation.Data)
                {
                    crList.Add(Convert(curCr));
                }
            }

            res.Update(obj.Version, mdList, crList, obj.ExpirationMins);

            return res;
        }

        /// <summary>
        /// Converts test metadata object "MrsMetadata" to delivery service metadata object "Metadata"
        /// </summary>
        /// <param name="obj">Test MrsMetadata object</param>
        /// <returns>Delivery Service Metadata object</returns>
        public static Metadata Convert(MrsMetadata obj)
        {
            if (obj == null)
            {
                return null;
            }

            Metadata res = new Metadata()
            {
                Name = obj.Name,
                Value = obj.Value
            };
            return res;
        }

        /// <summary>
        /// Converts Delivery Service Metadata object to test MrsMetadata object
        /// </summary>
        /// <param name="obj">Delivery Service Metadata object</param>
        /// <returns>Test MrsMetadata object</returns>
        public static MrsMetadata Convert(Metadata obj)
        {
            if (obj == null)
            {
                return null;
            }

            MrsMetadata res = new MrsMetadata(obj.Name, obj.Value);
            return res;
        }

        /// <summary>
        /// Converts the List of Delivery Service Metadata objects to list of Test MrsMetadata objects
        /// </summary>
        /// <param name="obj">List of delivery service Metadata objects</param>
        /// <returns>List of test MrsMetadata objects</returns>
        public static IList<MrsMetadata> Convert(IList<Metadata> obj)
        {
            if (obj == null)
            {
                return null;
            }

            MrsMetadata[] res = new MrsMetadata[obj.Count];
            for (int idx = 0; idx < obj.Count; idx++)
            {
                res[idx] = Convert(obj[idx]);
            }

            return res;
        }

        /// <summary>
        /// Coverts Test MrsCategoryReputation object to Delivery Service CategoryReputation object
        /// CategoryReputation object holds the category id, category info and confidence 
        /// </summary>
        /// <param name="obj">Test MrsCategoryReputation object</param>
        /// <returns>Delivery Service CategoryReputation object</returns>
        public static CategoryReputation Convert(MrsCategoryReputation obj)
        {
            if (obj == null)
            {
                return null;
            }

            CategoryReputation res = new CategoryReputation()
            {
                CategoryId = obj.CategoryId,
                Confidence = obj.Confidence
            };

            if (obj.CategoryInfo != null)
            {
                res.CategoryInfo = new Metadata[obj.CategoryInfo.Count];
                for (int idx = 0; idx < obj.CategoryInfo.Count; idx++)
                {
                    res.CategoryInfo[idx] = Convert(obj.CategoryInfo[idx]);
                }

            }
            return res;
        }

        /// <summary>
        /// Converts Delivery Service CategoryReputatation object to test MrsCategoryReputation object
        /// </summary>
        /// <param name="obj">Delivery Service CategoryReputation object</param>
        /// <returns>Test MrsCategoryReputation object</returns>
        public static MrsCategoryReputation Convert(CategoryReputation obj)
        {
            if (obj == null)
            {
                return null;
            }

            MrsCategoryReputation res = new MrsCategoryReputation(
                obj.CategoryId,
                obj.Confidence,
                Convert(obj.CategoryInfo)
            );

            return res;
        }

        /// <summary>
        /// Converts the test MrsLoookupRequest object to delivery service LookupRequest object
        /// </summary>
        /// <param name="obj">Test MrsLookupRequest object</param>
        /// <returns>Delivery Service LookupRequest object</returns>
        public static LookupRequest Convert(MrsLookupRequest obj)
        {
            if (obj == null)
            {
                return null;
            }

            LookupRequest res = new LookupRequest();

            res.Type = obj.Type;

            if (obj.SearchFields != null)
            {
                res.SearchableFields = new Metadata[obj.SearchFields.Count];
                for (int idx = 0; idx < obj.SearchFields.Count; idx++)
                {
                    res.SearchableFields[idx] = Convert(obj.SearchFields[idx]);
                }
            }

            return res;
        }

        /// <summary>
        /// Converts Delivery Service LookupRequest object to test MrsLookupRequest object
        /// </summary>
        /// <param name="obj">Delivery Service LookupRequest object</param>
        /// <returns>Test MrsLookupRequest object</returns>
        public static MrsLookupRequest Convert(LookupRequest obj)
        {
            if (obj == null)
            {
                return null;
            }

            MrsLookupRequest res = new MrsLookupRequest(obj.Type);

            if (obj.SearchableFields != null)
            {
                res.SearchFields = new MrsMetadata[0];
                foreach (Metadata curMd in obj.SearchableFields)
                {
                    res.SearchFields.Add(Convert(curMd));
                }
            }

            return res;
        }

        /// <summary>
        /// Converts DeliveryService LookupResponse object to test MrsLookupResponse object
        /// LookupResponse object contains the sequence number, status, telemetry, expiration mins and the matches related to the request
        /// </summary>
        /// <param name="obj">Delivery Service LookupResponse object</param>
        /// <returns>Test MrsLookupResponse object</returns>
        public static MrsLookupResponse Convert(LookupResponse obj)
        {
            if (obj == null)
            {
                return null;
            }

            List<MrsReputationObject> matches = null;

            if (obj.Matches != null)
            {
                matches = new List<MrsReputationObject>();

                foreach (ReputationObject curObj in obj.Matches)
                {
                    matches.Add(Convert(curObj));
                }
            }

            return new MrsLookupResponse(obj.SeqNumber, obj.Status, matches, obj.ExpirationMins);
        }

        /// <summary>
        /// Converts test MrsFilter object to Delivery Service Filter object
        /// Filter contains the last stamp, option to filter by metadata or category
        /// </summary>
        /// <param name="obj">Test MrsFilter object</param>
        /// <returns>Delivery Service Filter object</returns>
        public static Filter Convert(MrsFilter obj)
        {
            Filter res = null;
            if ((obj.LastStamp.HasValue) || (obj.Metadata != null) || (obj.Types != null) || obj.Categories != null)
            {
                res = new Filter();

                res.LastStamp = obj.LastStamp;

                if (obj.Metadata != null)
                {
                    res.RequestedMetadata = new string[obj.Metadata.Count];
                    for (int idx = 0; idx < obj.Metadata.Count; idx++)
                    {
                        res.RequestedMetadata[idx] = obj.Metadata[idx];
                    }
                }

                if (obj.Categories != null)
                {
                    res.RequestedCategory = new int[obj.Categories.Count];
                    for (int idx = 0; idx < obj.Categories.Count; idx++)
                    {
                        res.RequestedCategory[idx] = obj.Categories[idx];
                    }
                }

                if (obj.Types != null)
                {
                    res.RequestedData = new DataType[obj.Types.Count];
                    for (int idx = 0; idx < obj.Types.Count; idx++)
                    {
                        res.RequestedData[idx] = ((DataType)(obj.Types[idx]));
                    }
                }
            }
            return res;
        }
    }
}